package com.linkoog.devtools.metadata.raw_index;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.linkoog.devtools.metadata.DocumentMetadata;
import com.linkoog.devtools.raw_index.PsiRawFileIndex;
import com.linkoog.devtools.type.Type;
import com.linkoog.devtools.type.TypeParser;
import com.linkoog.devtools.type.javadoc.JavaDoc;
import com.linkoog.devtools.type.javadoc.JavaDocParser;
import com.linkoog.devtools.type.swagger.JavaSwaggerParser;
import com.linkoog.devtools.utils.PsiMethodUtil;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class MethodMetadata extends DocumentMetadata<PsiMethod> implements PsiRawFileIndex.Item<PsiMethod>{


    private volatile List<Type> paramTypes;
    @JsonIgnore
    private Supplier<List<Type>> paramTypesSupplier;
    private volatile Type returnType;
    @JsonIgnore
    private Supplier<Type> returnTypeSupplier;


    @JsonIgnore
    protected PsiMethod psiMethod;

    public MethodMetadata(@NotNull String filePath, int psiElementOffset, @NotNull String name) {
        super(filePath, psiElementOffset, name);
    }

    @JsonCreator
    public MethodMetadata(@JsonProperty("filePath")@NotNull String filePath,
                          @JsonProperty("psiElementOffset") int psiElementOffset,
                          @JsonProperty("name")@NotNull String name,
                          @JsonProperty("paramTypes") List<Type> paramTypes,
                          @JsonProperty("returnType") Type returnType
    ) {
        super(filePath, psiElementOffset, name);
        this.paramTypes = paramTypes;
        this.returnType = returnType;
    }

    public MethodMetadata(@NotNull String filePath,
                          int psiElementOffset,
                          @NotNull String name,
                          Supplier<List<Type>> paramTypesSupplier,
                          Supplier<Type> returnTypeSupplier
    ) {
        super(filePath, psiElementOffset, name);
        this.paramTypesSupplier = paramTypesSupplier;
        this.returnTypeSupplier = returnTypeSupplier;
    }



    @Override
    public boolean initPsiElement(@NotNull Project project, @Nullable PsiMethod psiMethod){
        if (this.psiMethod != null) return true;

        if (psiMethod != null){
            this.psiMethod = psiMethod;
            return true;
        }

        PsiMethod psi = PsiMethodUtil.findPsiMethod(project, filePath, psiElementOffset);
        if (psi != null){
            this.psiMethod = psi;
            return true;
        }

        return false;
    }


    @Override @JsonIgnore @NotNull
    public PsiMethod getPsiElement() {
        return psiMethod;
    }

    @NotNull
    public List<Type> getParamTypes() {
        if(this.paramTypes == null) {
            synchronized (this) {
                if(this.paramTypes == null) {
                    if(this.paramTypesSupplier != null) {
                        this.paramTypes = this.paramTypesSupplier.get();
                    }else {
                        List<Type> paramTypes = new ArrayList<>();
                        PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
                        for (PsiParameter parameter : parameters) {
                            Type paramType = TypeParser.parse(parameter.getType());
                            paramTypes.add(paramType);
                        }
                        this.paramTypes =  paramTypes;
                    }
                }
            }
        }

        return paramTypes;
    }

    public Type getReturnType() {
        if(this.returnType == null) {
            synchronized (this) {
                if(this.returnType == null) {
                    if(this.returnTypeSupplier != null) {
                        this.returnType = this.returnTypeSupplier.get();
                    } else {
                        this.returnType = TypeParser.parse(psiMethod.getReturnType());
                    }
                }
            }
        }
        return this.returnType;
    }

    public JavaDoc getJavaDoc() {
        if(javaDoc == null) {
            synchronized (this) {
                if(javaDoc == null) {
                    if(javaDocSupplier != null) {
                        javaDoc = javaDocSupplier.get();
                    } else {
                        javaDoc = JavaDocParser.parse(psiMethod.getDocComment());
                    }
                }
            }
        }
        return javaDoc;
    }

    public String getSwaggerComment() {
        if(swaggerComment == null) {
            synchronized (this) {
                if(swaggerComment == null) {
                    if(swaggerCommentSupplier != null) {
                        swaggerComment = swaggerCommentSupplier.get();
                    } else {
                        swaggerComment = JavaSwaggerParser.parseMethod(psiMethod);
                    }
                }
            }
        }
        return swaggerComment;
    }
}
